/**
 * sugar 糖，顾名思义都是一些方便使用的方法
 * 以及解析项目中一些简化写法
 * 比如路由可以直接使用字符串引用，可以极大的简化引用控制器
 */
import path from 'path'
import fs from 'fs'
import module from "module"

import ConfigHandler from '../init/ConfigHandler'

interface monitoringOptions{
    rules?:Function;
    filearr?:string[];
    hotDeployment?:boolean;
}

export = class {
    //解析语法糖路径
    static path(str:string,option:{
        superNumber?:number,
        prefix?:string,
        pathSugar?:Record<string,string>
    }={
        superNumber: 2,
        prefix: '',
        pathSugar: {}
    }):string{
        if(str === undefined) return '';
        try{
            // @ts-ignore
            return module._resolveFilename(str);
        }catch(err){
            option.pathSugar || (option.pathSugar={})
            option.prefix    || (option.prefix="")
            option.superNumber || (option.superNumber = 2)
            //替换路径糖
            Object.keys(option.pathSugar?option.pathSugar:ConfigHandler.getpathSugar()).forEach(res=>{
                try{
                    str = str.replace(res, ConfigHandler.getpathSugar(res) as string);
                }catch(err){
                    console.log(str)
                }
            })
            if(str[0] === "."){
                //这是相对路径，把调用者的父路径带上,组合成完整路径
                str = path.join(this.super_path(option.superNumber),str,option.prefix);
            }else if(!/^[A-Z]:|^[\/]/.test(str)){
                // 这是项目路径
                str = path.join(process.cwd(),str,option.prefix);
            }else{
              // 不是相对路径并且不是项目路径，那只能是绝对路径，但是绝对路径应该是走不到这里来的
              str = path.join(str,option.prefix);
            }
            return str;
        }
        
    }

    //1代表的是自身的位置，2代表调用者的位置
    static super_path(number:number=2,bool=false) {
        Error.prepareStackTrace = function(_, stack){ return stack; };
        let err = new Error;
        let stack:any = err.stack;
        let filePath = stack[number--];
        
        // while(filePath==undefined){
        //   filePath = stack[number--];
        // }
        //bool为真返回完整的文件路径
        if(bool){
            return filePath.getFileName();
        }else{
            //返回父路径
            let sup = path.dirname(filePath.getFileName());
            if(sup.startsWith("file:"))sup=sup.substring(8)
            return this.url(sup);
        }
    }

    //把中文路径正常解析出来
    static url(str:any){
        if (!/%[A-Za-z0-9]{2}/.test(str)) { return str; }
        return Array.from(Buffer.from(str.match(/%[A-Za-z0-9]{2}/g)
            .join("").replace(/%/g, ""), "hex").toString()).
        reduce((par:string, e:string) => {
            //e如果是中文需要3对
            if(/[^\x00-\xff]/.test(e)){return par.replace(/(%[A-Za-z0-9]{2}){3}/, e)}
            return par.replace(/%[A-Za-z0-9]{2}/, e);
        }, str);
    }


    static pars_watch(folder:string|string[]|any,option?:monitoringOptions|any,callback?:(eventType:string,filepath:string)=>void){
        if(typeof folder === 'string') folder = Array(folder);
        let rules:Function,filearr:string[],hotDeployment:boolean=true;
        if(typeof option === "function"){
            callback = option;
            rules   =   ()=>true;
            filearr =   ["node_modules"];
        }else{
            rules = option.rules || (()=>true);
            filearr = option.filearr || ["node_modules"];
            hotDeployment = option.hotDeployment!==undefined?option.hotDeployment:true;
        }
        return {rules,filearr,hotDeployment,callbacks:callback}
    }


    // 同步方法
    static async watchSync(folder:string|string[]|any,option?:monitoringOptions|any,callback?:(eventType:string,filepath:string)=>void){
        let {rules,filearr,hotDeployment=true,callbacks} = this.pars_watch(folder,option,callback);
        callback = callbacks;

        //遍历需要执行注解的文件夹============================================
        for(let address of folder){
            // 解析一下路径
            address = this.path(address);
            // 判断是文件还是文件夹
            if(fs.lstatSync(address).isDirectory()){
                // 这是文件夹
                let run = (cycle?:any)=>{
                    try{
                        //读取文件夹中的目录
                        for(let name of fs.readdirSync(address)){
                            // 合成路径
                            let filepath = path.join(address,name);
                            if(filearr.includes(filepath)) continue;
                            filearr.push(filepath);
                            // 判断是否为文件夹
                            if(fs.lstatSync(filepath).isDirectory()){
                                //这是文件夹 递归自己
                                this.watch(filepath,{rules,filearr},callback);
                            }else{
                                //执行规则
                                if(! rules(filepath)) continue;
                                //监控文件
                                hotDeployment && this.watchFile(filepath,callback,{filearr})
                            }
                        }
                    }catch(err){
                        console.log(err)
                        console.log("文件夹",address,"被删除");
                        //停止循环
                        cycle && clearInterval(cycle);
                    }
                }
                // 同步运行一次
                run();
                if(hotDeployment){
                    let cycle = setInterval( ()=> {
                        run(cycle);
                    },1000)    
                }
            }else{
                //这是一个文件
                //执行规则
                if(! rules(address)) return;
                //监控文件
                hotDeployment && this.watchFile(address,callback,{filearr})
            }
        }
    }


    /**
     * 监控文件夹以及子文件中的所有文件
     * 同时监控新增文件，以及删除文件
     * folder 需要监控的文件夹
     * callback(eventType,filepath) :更改类型 rename重命名 delete删除 change修改
     * filearr 是一个用来保存所有处于监控状态的文件，可以手动传一个空数组，也可以不传
     * rules 是用于验证规则，只有通过规则的文件才对其进行监控
     */
    static async watch(folder:string|string[]|any,option?:monitoringOptions|any,callback?:(eventType:string,filepath:string)=>void){
        if(!folder){return}
        // 解析参数=============================================判断有没有选项
        if(typeof folder === 'string') folder = Array(folder);
        let {rules,filearr,hotDeployment=true,callbacks} = this.pars_watch(folder,option,callback);
        callback = callbacks;
        
        //遍历需要执行注解的文件夹============================================
        for(let address of folder){
            // 解析一下路径
            address = this.path(address);
            // 判断是文件还是文件夹
            if(fs.lstatSync(address).isDirectory()){
                // 这是文件夹
                let run = async (cycle?:any)=>{
                    try{
                        //读取文件夹中的目录
                        for(let name of fs.readdirSync(address)){
                            // 合成路径
                            let filepath = path.join(address,name);
                            if(filearr.includes(filepath)) continue;
                            filearr.push(filepath);
                            // 判断是否为文件夹
                            if(fs.lstatSync(filepath).isDirectory()){
                                //这是文件夹 递归自己
                                this.watch(filepath,{rules,filearr},callback);
                            }else{
                                //执行规则
                                if(!await rules(filepath)) continue;
                                //监控文件
                                hotDeployment && this.watchFile(filepath,callback,{filearr})
                            }
                        }
                    }catch(err){
                        console.log(err)
                        console.log("文件夹",address,"被删除");
                        //停止循环
                        cycle && clearInterval(cycle);
                    }
                }
                // 同步运行一次
                await run();
                if(hotDeployment){
                    let cycle = setInterval( async ()=> {
                        run(cycle);
                    },1000)    
                }
            }else{
                //这是一个文件
                //执行规则
                if(!await rules(address)) return;
                //监控文件
                hotDeployment && this.watchFile(address,callback,{filearr})
            }
        }
    }

    //判断是否重命名了
    static isRename(folder:string,filearr:string[],stats){
        for(let name of fs.readdirSync(folder)){
            let filepath = path.resolve(folder,name);
            if(!filearr.includes(filepath)){
                //这是新增的文件，判断一下是不是之前的文件
                let sta = fs.statSync(filepath);
                if(sta.birthtimeMs===stats.birthtimeMs){
                    return true;
                }
            }
        }
        return false;
    }

    //监控单个文件
    static watchFile(filepath:string,callback:Function,{filearr}:any){
        //执行监控
        fs.watchFile(filepath,{interval:100},async (current,prev)=>{
            try{
                if(current.dev===0){
                    //被删除就停止监控这个文件,然后调用一次回调
                    fs.unwatchFile(filepath)
                    //检测一下是否为重命名了
                    if(this.isRename(path.dirname(filepath),filearr,prev))
                        await callback("rename",filepath);
                    else
                        await callback("delete",filepath);
                }else {
                    await callback("change",filepath);
                }
            }catch(e){
                console.log(e.message);
                console.log(e)
            }
        })
    }

    // 遍历文件夹
    static async traverseDir(pathDir:string,callback:Function){
        for(let e of fs.readdirSync(pathDir)){
            let filepath = path.join(pathDir,e);
            if(fs.statSync(filepath).isFile()){
                await callback(filepath);
            }else
            await this.traverseDir(filepath,callback)
        }
    }
    static traverseDirSync(pathDir:string,callback:Function){
        for(let e of fs.readdirSync(pathDir)){
            let filepath = path.join(pathDir,e);
            if(fs.statSync(filepath).isFile()){
                callback(filepath);
            }else
            this.traverseDir(filepath,callback)
        }
    }

    // 判断是否是一个异步方法
    static isAsync (func:Function) {
        const string = func.toString().trim();
        return !!(
            // 本地的
            string.match(/^async /) ||
            // babel (this may change, but hey...)
            string.match(/return _ref[^\.]*\.apply/)
            // 插入另一个脏转换器检查
            // 还有其他更复杂的情况，可能需要您检查返回行*承诺*
        );
    }

    // 获取一个文件是什么具体类型 function object class
    static getFileType(path:string){
        if(!fs.existsSync(path)){throw new Error("文件不存在")}
        let data = fs.readFileSync(path,"utf8");
        // 开始正则匹配
        // 判断是不是 ^((?!//|/*).)*module.exports[ ]*=
        if(/^((?!\/\/|\/\*).)*module.exports[ \r\n]*=/g.test(data)){
            // 这是一个整体的代码，继续判断是类还是对象
            let sj = data.match(/((?!\/\/|\/\*).)*(module.exports[ \r\n]*=|export[ ]+default)[ \r\n]*[\w\d(.){}]+/g)?.[0]
                .replace(/((?!\/\/|\/\*).)*module.exports[ \r\n]*=[ \r\n]*/g,"")
            if(sj.startsWith("class")){
                // 这是一个类
                return "class";
            }else if(sj.startsWith("function") || /^(.*?)/g.test(sj)){
                // 这是一个方法
                return "function";
            }else if(sj.startsWith("{")){
                // 这是一个对象
                return "object";
            }else{
                // 这是最难的一个，赋予的一个变量
                return this.getVariableType(data,sj);
            }
        }else{
            // 匹配exports.
            if(/((?!\/\/|\/\*).)*(exports\.|export )/g.test(data)) return "object";
        }
    }

    // 获取变量类型
    static getVariableType(data:string,name:string){
        // 正则匹配
        if(new RegExp(`((?!\/\/|\/\*).)*function[ ]*${name}`,"g").test(data)) return "function";
        if(new RegExp(`((?!\/\/|\/\*).)*class[ ]*${name}`,"g").test(data)) return "class";
        if(new RegExp(`((?!\/\/|\/\*).)*(let|var|const)+[ ]*${name}[ ]*=[ ]*{`,"g").test(data)) return "object";
        name = data.match(new RegExp(`((?!\/\/|\/\*).)*(let|var|const)+[ ]+${name}[ ]*=[ \r\n]*[a-zA-Z0-9\(\)= >\{\}]+`,"g"))?.[0]
            .replace(new RegExp(`((?!\/\/|\/\*).)*(let|var|const)+[ ]+${name}[ ]*=[ \r\n]*`,"g"),"")
        if(name) return null;
        if(name.startsWith("function") || /\(.*?\)[ ]*=>[ ]*/g.test(name)) return "function";
        if(name.startsWith("{")) return "object";
        if(name.startsWith("class")) return "class";
        return this.getVariableType(data,name)
    }

    // pop 删除返回数组最后一个
    static pop(arr:Array<any>){
        return arr.pop();
    }
}